/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.actions.zip.signing;

import com.softwarecraftsmen.orogeny.actions.keys.ConvenientKey;
import org.jetbrains.annotations.NotNull;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.x509.AlgorithmId;
import static sun.security.x509.AlgorithmId.get;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertInfo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static java.lang.String.format;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Signature;
import static java.security.Signature.getInstance;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import static java.util.Locale.UK;

public class SignatureBlockFile
{
	private final PrivateKey privateKey;
	private final byte[] signatureFileContent;
	private final X509Certificate[] signerCertificateChain;
	private final String privateKeyAlgorithm;

	public SignatureBlockFile(final @NotNull PrivateKey privateKey, final @NotNull byte[] signatureFileContent, final @NotNull X509Certificate... signerCertificateChain)
	{
		this.privateKey = privateKey;
		this.signatureFileContent = signatureFileContent;
		this.signerCertificateChain = signerCertificateChain;
		privateKeyAlgorithm = privateKey.getAlgorithm();
	}

	@NotNull
	public String signatureBlockFileName(final @NotNull ConvenientKey convenientKey)
	{
		return convenientKey.signatureBlockFileName(privateKeyAlgorithm);
	}

	public byte[] content()
	{
		try
		{
			final String signatureAlgorithm = format(UK, "SHA1with%1$s", privateKeyAlgorithm);
			return signSignature(signerCertificateChain, signatureAlgorithm, computeSignature(signatureAlgorithm));
		}
		catch (NoSuchAlgorithmException e)
		{
			throw new IllegalStateException(e);
		}
		catch (CertificateException e)
		{
			throw new IllegalStateException(e);
		}
		catch (IOException e)
		{
			throw new IllegalStateException(e);
		}
		catch (Exception e)
		{
			throw new IllegalStateException(e);
		}
	}

	@NotNull
	private byte[] computeSignature(final String signatureAlgorithm)
	{
		final Signature signature;
		try
		{
			signature = getInstance(signatureAlgorithm);
		}
		catch (NoSuchAlgorithmException e)
		{
			throw new IllegalStateException(format(UK, "There is no signature algorithm for %1$s", signatureAlgorithm), e);
		}
		try
		{
			signature.initSign(privateKey);
		}
		catch (InvalidKeyException e)
		{
			throw new IllegalStateException(format(UK, "The private key %1$s is invalid", privateKey), e);
		}
		try
		{
			signature.update(signatureFileContent);
			return signature.sign();
		}
		catch (SignatureException e)
		{
			throw new IllegalStateException("Could not create signature", e);
		}
	}

	private byte[] signSignature(final @NotNull X509Certificate[] signerCertificateChain, final @NotNull String signatureAlgorithm, final @NotNull byte[] signature) throws NoSuchAlgorithmException, CertificateException, IOException
	{
		String str2 = null;
		String dsaOrRsa = null;
		int i = signatureAlgorithm.indexOf("with");
		if (i > 0)
		{
			str2 = signatureAlgorithm.substring(0, i);
			int j = signatureAlgorithm.indexOf("and", i + 4);
			if (j > 0)
			{
				dsaOrRsa = signatureAlgorithm.substring(i + 4, j);
			}
			else
			{
				dsaOrRsa = signatureAlgorithm.substring(i + 4);
			}
		}

		final Principal issuerPrincipal = signerCertificateChain[0].getIssuerDN();
		final Principal principal;
		if (!(issuerPrincipal instanceof X500Name))
		{
			principal = (Principal) ((X509CertInfo) new X509CertInfo(signerCertificateChain[0].getTBSCertificate())).get("issuer.dname");
		}
		else
		{
			principal = issuerPrincipal;
		}

		return toBytes(new PKCS7(new AlgorithmId[]{get(str2)}, new ContentInfo(ContentInfo.DATA_OID, null), signerCertificateChain, new SignerInfo[]{new SignerInfo((X500Name) principal, signerCertificateChain[0].getSerialNumber(), get(str2), get(dsaOrRsa), signature)}));
	}

	private byte[] toBytes(final PKCS7 pkcs7)
	{
		final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		try
		{
			pkcs7.encodeSignedData(byteArrayOutputStream);
		}
		catch (IOException shouldNeverHappen)
		{
			throw new IllegalStateException(shouldNeverHappen);
		}
		return byteArrayOutputStream.toByteArray();
	}
}
